package folder.main.exp2.algorithms;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;

import folder.model.network.Network;
import folder.model.node.Node;
import folder.model.virtual.VirtualNetwork;

public class Algorithm2
{
	private Network network;
	private double delayThreshold;
	private List< VirtualNetwork > virtualNetworks;
	protected Comparator< Node > comparator;
	
	private int numberControllers;
	public int getNumberControllers(){ return this.numberControllers; }
	
	private double delayMeanControllerEmpty;
	public double getDelayMeanControllerEmpty(){ return this.delayMeanControllerEmpty; }
	
	private double delayMeanNormalEmpty;
	public double getDelayMeanNormalEmpty(){ return this.delayMeanNormalEmpty; }
	
	private double differenceDelaysEmpty;
	public double getDifferenceDelaysEmpty(){ return this.differenceDelaysEmpty; }
	
	private double delayMeanControllerFull;
	public double getDelayMeanControllerFull(){ return this.delayMeanControllerFull; }
	
	private double delayMeanNormalFull;
	public double getDelayMeanNormalFull(){ return this.delayMeanNormalFull; }
	
	private double differenceDelaysFull;
	public double getDifferenceDelaysFull(){ return this.differenceDelaysFull; }
	
	private double delayLongerMeanControllerEmpty;
	public double getDelayLongerMeanControllerEmpty(){ return this.delayLongerMeanControllerEmpty; }
	
	private double delayLongerMeanNormalEmpty;
	public double getDelayLongerMeanNormalEmpty(){ return this.delayLongerMeanNormalEmpty; }
	
	private double differenceDelaysLongerEmpty;
	public double getDifferenceDelaysLongerEmpty(){ return this.differenceDelaysLongerEmpty; }
	
	private double delayLongerMeanControllerFull;
	public double getDelayLongerMeanControllerFull(){ return this.delayLongerMeanControllerFull; }
	
	private double delayLongerMeanNormalFull;
	public double getDelayLongerMeanNormalFull(){ return this.delayLongerMeanNormalFull; }
	
	private double differenceDelaysLongerFull;
	public double getDifferenceDelaysLongerFull(){ return this.differenceDelaysLongerFull; }
	
	private double occupation;
	public double getOccupation(){ return this.occupation; }
	
	protected boolean checkAlgorithm = false;
	public boolean getCheckAlgorithm(){ return this.checkAlgorithm; }
	
	public Algorithm2( Network network, double delayThreshold, List< VirtualNetwork > virtualNetworks )
	{
		this.network = network;
		this.delayThreshold = delayThreshold;
		this.virtualNetworks = virtualNetworks;
	}
	
	protected boolean run()
	{	
		if( !this.runAlgorithm() )
			return false;
			
		this.setEvaluation();
		
		this.runEvaluation();
		this.setResultsEmpty();
		
		if( !this.allocateVirtualNetworks() )
			return false;
		
		this.runEvaluation();
		this.setResultsFull();
		
		this.release();
		
		return this.getCheckDelays();
	}
	
	private List< Node > nodesSorted;
	private LinkedHashMap< Node, Node > nodesMatched;
	private LinkedHashMap< Node, Node > nodesMatchedFinal;
	
	private boolean runAlgorithm()
	{
		this.network.setPathsNetwork();
		this.setNodesSorted();
		this.setNodesMatched();
		
		if( this.nodesMatched == null )
			return false;
		
		if( this.getClass().getSimpleName().equals( "Random" ) )
			this.nodesMatchedFinal = this.nodesMatched;
		else
			this.setNodesMatchedFinal();
		
		this.setNodesMatchedFinal();
		
		return true;
	}
		
	private void setNodesSorted()
	{
		nodesSorted = new ArrayList< Node >();
		
		for( Node node: this.network.getNodes() )
			nodesSorted.add( node );
		
		Collections.sort( nodesSorted, this.comparator );
	}
	
	private void setNodesMatched()
	{
		nodesMatched = new LinkedHashMap< Node, Node >();
		List< Node > nodesSelected = new ArrayList< Node >();
		List< Node > nodesToMatch = new ArrayList< Node >();
		
		for( Node node: network.getNodes() )
			nodesToMatch.add( node );
		
		while( !nodesToMatch.isEmpty() && !nodesSorted.isEmpty() )
		{
			Node firstSortedNode = nodesSorted.get( 0 );
			
			if( nodesSorted.contains( firstSortedNode ) )
				nodesSorted.remove( firstSortedNode );
			
			List< Node > selectedNodesToRemove = new ArrayList< Node >();
			
			for( Node nodeToMatch: nodesToMatch )
			{
				double pathDelayToNodeToMatch = firstSortedNode.getDelayToNodeTarget( nodeToMatch );
				
				if( ( !nodeToMatch.equals( firstSortedNode ) ) && ( pathDelayToNodeToMatch <= delayThreshold ) )
				{
					Node selectedNodeToRemove = null;
					
					if( nodesMatched.containsKey( firstSortedNode ) )
					{
						selectedNodeToRemove = nodesMatched.get( firstSortedNode );
						nodesMatched.remove( firstSortedNode );
					}
					
					if( selectedNodeToRemove != null && !nodesMatched.containsValue( selectedNodeToRemove ) )
					{
						if( nodesSelected.contains( selectedNodeToRemove ) )
							nodesSelected.remove( selectedNodeToRemove );
						
						double shortestDelay = Double.POSITIVE_INFINITY;
						Node selectedNodeWithShortestDelay = null;
						
						for( Node selectedNode: nodesSelected )
						{
							double pathDelayToSelectedNodeToRemove = selectedNode.getDelayToNodeTarget( selectedNodeToRemove );
							
							if( ( pathDelayToSelectedNodeToRemove < shortestDelay ) && ( pathDelayToSelectedNodeToRemove <= delayThreshold ) )
							{
								shortestDelay = pathDelayToSelectedNodeToRemove;
								selectedNodeWithShortestDelay = selectedNode;
							}
						}
						
						if( selectedNodeWithShortestDelay != null )
							nodesMatched.put( selectedNodeToRemove, selectedNodeWithShortestDelay );
						else
							selectedNodesToRemove.add( selectedNodeToRemove );
					}
					
					nodesMatched.put( nodeToMatch, firstSortedNode );
					
					if( !nodesSelected.contains( firstSortedNode ) )
						nodesSelected.add( firstSortedNode );
				}
			}
			
			if( !selectedNodesToRemove.isEmpty() )
				for( Node controllerToReturn: selectedNodesToRemove )
					nodesToMatch.add( controllerToReturn );
			
			Iterator< Entry< Node, Node > > iterator2 = nodesMatched.entrySet().iterator();
			
			while( iterator2.hasNext() )
			{
				Entry< Node, Node > entry = iterator2.next();
				Node controlled = entry.getKey();
				Node controller = entry.getValue();
				
				if( nodesToMatch.contains( controlled ) )
					nodesToMatch.remove( controlled );
				
				if( nodesToMatch.contains( controller ) )
					nodesToMatch.remove( controller );
			}
		}
		
		if( !nodesToMatch.isEmpty() )
			nodesMatched = null;
	}
	
	private void setNodesMatchedFinal()
	{
		nodesMatchedFinal = new LinkedHashMap< Node, Node >();
		nodesMatchedFinal.putAll( nodesMatched );
		
		List< Node > nodesController = this.getNodesController( nodesMatched );
		
		Iterator< Entry< Node, Node > > iterator = nodesMatched.entrySet().iterator();
		
		while( iterator.hasNext() )
		{	
			Entry< Node, Node > entry = iterator.next();
			Node controlled = entry.getKey();
			Node controller = entry.getValue();
			
			double minDelay = controlled.getDelayToNodeTarget( controller );
			
			Node newController = null;
			
			for( Node candidate: nodesController )
				if( controlled.getDelayToNodeTarget( candidate ) <= minDelay )
				{
					minDelay = controlled.getDelayToNodeTarget( candidate );
					newController = candidate;
				}
			
			if( newController != null )
				nodesMatchedFinal.put( controlled, newController );
		}
		
		List< Node > nodesControllerFinal = this.getNodesController( nodesMatchedFinal );
		
		if( nodesControllerFinal.size() < nodesController.size() )
		{
			for( Node controller: nodesController )				
				if( !nodesControllerFinal.contains( controller ) )
				{
					double minDelay = delayThreshold;
					Node minDelayController = null;
					
					for( Node minController: nodesControllerFinal )
					{
						double delay = controller.getDelayToNodeTarget( minController );
						
						if( delay <= minDelay )
						{
							minDelay = delay;
							minDelayController = minController;
						}
					}
					
					if( minDelayController != null )
						nodesMatchedFinal.put( controller, minDelayController );
					else
					{
						nodesMatchedFinal = nodesMatched;
						break;
					}
				}
		}
	}
		
	private List< Node > getNodesController( LinkedHashMap< Node, Node > matches )
	{
		List< Node > controllers = new ArrayList< Node >();
		Iterator< Entry< Node, Node > > iterator = matches.entrySet().iterator();
				
		while( iterator.hasNext() )
		{
			Node controller = iterator.next().getValue();
			
			if( !controllers.contains( controller ) )
				controllers.add( controller );
		}
		
		return controllers;
	}
	
	private Evaluation evaluation;
	
	private void setEvaluation()
	{
		this.evaluation = Evaluation.evaluation( this.network, this.nodesMatchedFinal );
	}
	
	private void runEvaluation()
	{
		this.evaluation.run();
	}
	
	private void setResultsEmpty()
	{
		this.numberControllers = this.evaluation.getNumberNodesControllers();
		
		this.delayMeanControllerEmpty = this.evaluation.getDelayMeanController();
		this.delayMeanNormalEmpty = this.evaluation.getDelayMeanNormal();
		this.differenceDelaysEmpty = this.evaluation.getDifferenceDelays();
		
		this.delayLongerMeanControllerEmpty = this.evaluation.getDelayLongerMeanController();
		this.delayLongerMeanNormalEmpty = this.evaluation.getDelayLongerMeanNormal();
		this.differenceDelaysLongerEmpty = this.evaluation.getDifferenceDelaysLonger();
	}
		
	private boolean allocateVirtualNetworks()
	{
		for( VirtualNetwork virtualNetwork: virtualNetworks )
			if( !network.allocate( virtualNetwork ) )
				return false;
		
		return true;
	}
	
	private void setResultsFull()
	{
		this.numberControllers = this.evaluation.getNumberNodesControllers();
		
		this.delayMeanControllerFull = this.evaluation.getDelayMeanController();
		this.delayMeanNormalFull = this.evaluation.getDelayMeanNormal();
		this.differenceDelaysFull = this.evaluation.getDifferenceDelays();

		this.delayLongerMeanControllerFull = this.evaluation.getDelayLongerMeanController();
		this.delayLongerMeanNormalFull = this.evaluation.getDelayLongerMeanNormal();
		this.differenceDelaysLongerFull = this.evaluation.getDifferenceDelaysLonger();
		
		this.occupation = this.evaluation.getOccupationPercentage();
	}
	
	private void release()
	{
		for( VirtualNetwork virtualNetwork: virtualNetworks )
			network.release( virtualNetwork );
	}
	
	private boolean getCheckDelays()
	{
		if( this.numberControllers > 0 &&
			this.delayMeanControllerEmpty < Double.POSITIVE_INFINITY && 
			this.delayMeanControllerFull < Double.POSITIVE_INFINITY && 
			this.delayMeanNormalEmpty < Double.POSITIVE_INFINITY && 
			this.delayMeanNormalFull < Double.POSITIVE_INFINITY && 
			this.differenceDelaysEmpty < Double.POSITIVE_INFINITY && 
			this.differenceDelaysFull < Double.POSITIVE_INFINITY )
				return true;
			else
				return false;
	}
		
}
